#include "uniMesh.hpp"
#include "rectMesh.hpp"
#include "structMesh.hpp"
#include "mesh.hpp"
#include "gmshImport.hpp"
#include "gmshImport_testMSH.hpp"
#include "util.hpp"
#include "streamAccess.hpp"
#include "meshTypeFunctions.hpp"
#include "mesh_testFunctions.hpp"
#include "testFunctions.hpp"
#include <catch2/catch_test_macros.hpp>
#include <sstream>
#include <variant>

using namespace hms;


TEST_CASE( "isQuadrilateral: Testing whether mesh has only quadrilateral cells"){
	REQUIRE( hms::isQuadrilateral( testMeshMixed(true) ) == false );
	REQUIRE( hms::isQuadrilateral( testMeshRectangles(true) ));
	REQUIRE( hms::isQuadrilateral( testMeshTriangles(true) ) == false );

	/* both gmsh test meshes are actually quadrilateral. One is structured, the
	 * other one is not, but it is successfully recombined, with zero triangles
	 * */
	{
		std::stringstream ss;
		ss << testMSH;
		REQUIRE( hms::isQuadrilateral( gmsh::gmshToHms( ss ) ) );
	}{
		std::stringstream ss;
		ss << testMSH2;
		REQUIRE( hms::isQuadrilateral( gmsh::gmshToHms( ss ) ) );
	}
}

TEST_CASE( "isStructured: Testing whether mesh has regular topology" ){
	REQUIRE( !hms::isStructured( testMeshMixed(true) ) );
	REQUIRE( hms::isStructured( testMeshRectangles(true) ) );
	REQUIRE( !hms::isStructured( testMeshTriangles(true) ) );

	/* while both gmsh test meshes are quadrilateral, only the first one is
	 * structured */
	{
		std::stringstream ss;
		ss << testMSH;
		REQUIRE( hms::isStructured( gmsh::gmshToHms( ss ) ) );
	}{
		std::stringstream ss;
		ss << testMSH2;
		REQUIRE( !hms::isStructured( gmsh::gmshToHms( ss ) ) );
	}
}

TEST_CASE( "isRectangular: Testing whether mesh has only rectangular cells" ){
	REQUIRE( hms::isRectangular( testMeshMixed(true) ) == false );
	REQUIRE( hms::isRectangular( testMeshRectangles(true) ) );
	REQUIRE( hms::isRectangular( testMeshTriangles(true) ) == false );

	/* while both gmsh test meshes are quadrilateral, only the first one is
	 * rectangular */
	{
		std::stringstream ss;
		ss << testMSH;
		REQUIRE( hms::isRectangular( gmsh::gmshToHms( ss ) ) );
	}{
		std::stringstream ss;
		ss << testMSH2;
		REQUIRE( hms::isRectangular( gmsh::gmshToHms( ss ) ) == false );
	}
}

TEST_CASE( "isUniform: Testing whether mesh has uniformly rectangular cells" ){
	REQUIRE( hms::isUniform( testMeshMixed(true) ) == false );
	REQUIRE( hms::isUniform( testMeshRectangles(true) ) );
	REQUIRE( hms::isUniform( testMeshTriangles(true) ) == false );

	/* while both gmsh test meshes are quadrilateral, only the first one is
	 * uniform */
	{
		std::stringstream ss;
		ss << testMSH;
		REQUIRE( hms::isUniform( gmsh::gmshToHms( ss ) ) );
	}{
		std::stringstream ss;
		ss << testMSH2;
		REQUIRE( hms::isUniform( gmsh::gmshToHms( ss ) ) == false );
	}
}

#include "topology.hpp"
#include "meshFunctions.hpp"
#include "meshImportExport.hpp"
#include <iostream>
TEST_CASE( "meshToStructMesh: Converting hms::Mesh" ){
	
	Mesh mesh { createRectangularMesh(3,4,1,1) };

	std::vector<IdxVector> edgesAtVertices { hms::edgesAtVertices(mesh) };

	if ( auto corners = isStructured(mesh, edgesAtVertices) ){
		StructMesh sMesh {
			meshToStructMesh(std::move(mesh),edgesAtVertices,*corners)
		};
		Matrix2Xs vertices (2, 20);
		vertices <<
			0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3,
			0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4;

		REQUIRE( approxEqual( sMesh.vertices(), vertices ) );

		REQUIRE(exactlyEqual(sMesh.boundaryPatch("bottom").edges(), Array3i{ 0, 1, 2   } ) );
		REQUIRE(exactlyEqual(sMesh.boundaryPatch("top"   ).edges(), Array3i{12,13,14   } ) );
		REQUIRE(exactlyEqual(sMesh.boundaryPatch("left"  ).edges(), Array4i{15,19,23,27} ) );
		REQUIRE(exactlyEqual(sMesh.boundaryPatch("right" ).edges(), Array4i{18,22,26,30} ) );
	}
	else {
		std::cout << "Mesh is not structured!\n";
	}
}

void whatDoesItHold( const hms::MeshVariant& meshVar ){
	using namespace hms;
	std::cout << "Variant holds ";
	if ( std::holds_alternative<UniMesh>(meshVar) )
		std::cout << "UniMesh";
	if ( std::holds_alternative<RectMesh>(meshVar) )
		std::cout << "RectMesh";
	if ( std::holds_alternative<StructMesh>(meshVar) )
		std::cout << "StructMesh";
	if ( std::holds_alternative<Mesh>(meshVar) )
		std::cout << "Mesh";
	std::cout << "\n";
}

TEST_CASE("optimiseMeshType: Testing automatic mesh representation optimisation"
){
	using namespace hms;
	
	MeshVariant meshVar;

	SECTION( "UniMesh from Mesh" ){
		meshVar.emplace<Mesh>( createRectangularMesh(3,4,1,1) );

		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<UniMesh>(meshVar) );
	}
	SECTION( "UniMesh from StructMesh" ){
		Matrix2Xs vertices (2,12);
		vertices <<
			0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2,
			0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3;
		meshVar.emplace<StructMesh>(
			std::move(vertices), 2, 3
		);
		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<UniMesh>(meshVar) );
	}
	SECTION( "UniMesh from RectMesh" ){
		/* create uniformly spaced coordinates */
		VectorXs xCoords (3);
		VectorXs yCoords (4);
		xCoords << 0, 1, 2;
		yCoords << 0, 1, 2, 3;
		meshVar.emplace<RectMesh>(
			std::move(xCoords),
			std::move(yCoords)
		);
		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<UniMesh>(meshVar) );
	}
	SECTION( "RectMesh from Mesh" ){
		/* create vertices for non-uniform rectangular cells */
		Matrix2Xs vertices (2,12);
		vertices <<
			0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 1, 3,
			1, 1, 1, 3, 3, 3, 4, 4, 4, 8, 8, 8;
		/* create vertex indices for edges */
		Array2Xi edges (2,17);
		edges <<
			0, 1, 3, 4, 6, 7, 9,10, 0, 3, 6, 1, 4, 7, 2, 5, 8,
			1, 2, 4, 5, 7, 8,10,11, 3, 6, 9, 4, 7,10, 5, 8,11;
		meshVar.emplace<Mesh>(
			std::move(vertices),
			std::move(edges)
		);
		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<RectMesh>(meshVar) );
	}
	SECTION( "RectMesh from StructMesh" ){
		/* create vertices for non-uniform rectangular cells */
		Matrix2Xs vertices (2,12);
		vertices <<
			0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 1, 3,
			1, 1, 1, 3, 3, 3, 4, 4, 4, 8, 8, 8;
		meshVar.emplace<StructMesh>( std::move(vertices), 2, 3 );
		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<RectMesh>(meshVar) );
	}
	SECTION( "StructMesh from Mesh" ){
		/* create vertices for quadrilateral cells */
		Matrix2Xs vertices (2,12);
		vertices <<
			0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 1, 3,
			1, 1, 1, 3, 3, 3, 4, 4, 4, 8, 8, 8;
		/* distort the grid by translating every other vertex a bit */
		vertices(hms::all, Eigen::seqN(1,6,2)).colwise() += Vector2s{0.2,-0.3};
		/* create vertex indices for edges */
		Array2Xi edges (2,17);
		edges <<
			0, 1, 3, 4, 6, 7, 9,10, 0, 3, 6, 1, 4, 7, 2, 5, 8,
			1, 2, 4, 5, 7, 8,10,11, 3, 6, 9, 4, 7,10, 5, 8,11;
		meshVar.emplace<Mesh>( std::move(vertices), std::move(edges) );
		optimiseMeshType(meshVar);
		REQUIRE( std::holds_alternative<StructMesh>(meshVar) );
	}
}

/* learning to use std::variant */
/*
TEST_CASE( "Creating std::variant of mesh types" ){
	using namespace hms;
	MeshVariant mesh;
    
	std::cout << "Size of variant: " << sizeof(mesh) << "\n";
    whatDoesItHold(mesh);
	if ( std::holds_alternative<UniMesh>(mesh) ){
		mesh = UniMesh{3, 4, 1, 1};
		std::cout
			<< "Size of variant after UniMesh construction: "
			<< sizeof(mesh) << "\n";
		
		const UniMesh& uniMesh = std::get<UniMesh>(mesh);
		std::cout << "Size of UniMesh: " << sizeof(uniMesh) << "\n";
		// uniMesh.printInfo(); // works
	}
	std::cout << "Assigning RectMesh...\n";
	mesh = RectMesh{ VectorNs<4>{0,1,2,3}, VectorNs<5>{0,1,2,3,4} };
	whatDoesItHold(mesh);
	std::cout << "Size of variant after RectMesh assignment: "
		<< sizeof(mesh) << "\n";
	std::cout << "Size of RectMesh: " << sizeof(std::get<RectMesh>(mesh)) << "\n";

	std::cout << std::boolalpha << "StructMesh is...\n"
		<<"\tmove-constructible: "<< std::is_move_constructible_v<StructMesh> << "\n"
		<<"\tmove-assignable:    "<< std::is_move_assignable_v   <StructMesh> << "\n";

	std::cout << "Assigning StructMesh...\n";
	Matrix2Xs vertices (2,20);
	vertices <<
		0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3,
		0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4;
	
	mesh = StructMesh{ std::move(vertices), 3, 4 };
	whatDoesItHold(mesh);
	std::cout << "Size of variant after StructMesh assignment: "
		<< sizeof(mesh) << "\n";
	std::cout << "Size of StructMesh: " << sizeof(std::get<StructMesh>(mesh)) << "\n";
	
	std::cout << std::boolalpha << "Mesh is...\n"
		<<"\tmove-constructible: "<< std::is_move_constructible_v<Mesh> << "\n"
		<<"\tmove-assignable:    "<< std::is_move_assignable_v   <Mesh> << "\n";
	
	std::cout << "Assigning Mesh...\n";
	
	mesh = createRectangularMesh(3,4,1,1);

	whatDoesItHold(mesh);
	std::cout << "Size of variant after Mesh assignment: "
		<< sizeof(mesh) << "\n";
	std::cout << "Size of Mesh: " << sizeof(std::get<Mesh>(mesh)) << "\n";
}
*/